<?PHP
/**
 * OpenStation
 *
 * @Application : OpenTPL_Parse.class.php
 * @Encode : 65001 Unicode ( UTF-8 NO BOM )
 * @Description : OpenStation内置模板引擎核心解析类
 * @Author : HK <hkyyang@gmail.com>
 */

abstract class OpenTPL_Parse {

    // OpenTPL对象实例
    protected $openTPL = NULL;
    // 已经解析过别名的标签方法
    protected $Methods = array();
    // 编译完成方法缓存
    protected $compileMethod = array();
    // 模板标签开始符
    protected $left;
    // 模板标签结束符
    protected $right;
    // 条件表达式运算符号
    protected $comparison = array( ' nheq ' => ' !== ', ' heq ' => ' === ', ' neq ' => ' != ', ' eq ' => ' == ', ' egt ' => ' >= ', ' gt ' => ' > ', ' elt ' => ' <= ', ' lt ' => ' < ' );
    // 指定内部异常处理类
    protected $internalException = NULL;
    // 文本标签内容暂时存放
    protected $literalData = array();
    // 内部解析时行号
    public $InternalLine = 0;
    // 回调替换标记
    public $afterReplaceTags = array();

    // 文本标签内容临时替换标记
    const LITERAL_REPLACE_TAG = '<!--OPEN_TPL_LITERAL_REPLACE_TAG_%NUM%-->';
    // 变量函数分隔符
    const TPL_FUNC_SEP = '|';
    // 函数名截止符
    const TPL_FUNC_END = ':';
    // 参数分隔符
    const TPL_PARAM_SEP = ',';
    // 变量函数时指变量向指针
    const TPL_VAR_SELF = '%SELF';
    // 标签定界符左
    const TPL_DELIMITER_LEFT = '%LEFT';
    const TPL_DELIMITER_RIGHT = '%RIGHT';
    // 允许标签定界符和标签内容的最大间隔(空白)
    const TPL_PARSE_BLANK_SPACE = '';

    /**
     * 构造方法，初始化模板标签方法及别名
     *
     * @access protected
     * @param String : $content
     * @return String
     */
    public function __construct( $OpenTPL ) {
        $this -> openTPL = $OpenTPL;
        $this -> left = $this -> escapeDelimiter( $this -> openTPL -> left );
        $this -> right = $this -> escapeDelimiter( $this -> openTPL -> right );
        $this -> internalException = $this -> openTPL -> internalException;

        foreach( $this -> OpenMethods as $fun => $attr ) {
            $this -> Methods[ $this -> escapeDelimiter( $fun ) ] = $attr;
            if( ! empty( $attr[ 'alias' ] ) ) {
                $temp = $attr;
                unset( $attr[ 'alias' ] );
                // 别名方法绑定
                $attr[ 'bind' ] = $fun;
                foreach( explode( ',', $temp[ 'alias' ] ) as $alias ) {
                    $this -> Methods[ $this -> escapeDelimiter( $alias ) ] = $attr;
                }
            }
        }
    }

    /**
     * escapeDelimiter 方法, 转义模板定界符,用于正则表达式
     *
     * @access protected
     * @param String : $str
     * @return String
     */
    protected function escapeDelimiter( $str ) {
        return str_replace( array( '{', '}' , '(', ')', '|', '[', ']', '*' ), array( '\{', '\}', '\(', '\)', '\|', '\[', '\]', '\*' ), $str );
    }

    /**
     * parse 方法，模板内容解析入口
     *
     * @access public
     * @param String : $content, 模板内容
     * @param Stirng : $multiTpl, 来自模板内部载入的模板
     * @return String
     */
    public function parse( $content ) {

        // 解析主要数据
        $content = $this -> parseMain( $content );
        // 单行标签: 模板变量或其它标签
        $content = preg_replace( '/' . $this -> applyDelimiter( '(.+?)' ) . '(.*?)' . $this -> getLineTagReg() . '/e', "\$this -> replaceInternalRow( '\$1', '\$2', '\$3' )", $content );
        // 删除内部标记
        $content = preg_replace( '/' . $this -> getLineTagReg() . '/e', "\$this -> replaceInternalRow( NULL, NULL, 0 )", $content );
        // 还原临时替换的文本内容
        $content = preg_replace( '/' . str_replace( '%NUM%', '(\d+)', self :: LITERAL_REPLACE_TAG ) . '/e', "\$this -> restoreLiteralDataCallBack( '\$1' )", $content );
        // 回调替换标记
        foreach( $this -> afterReplaceTags as $k => $v ) {
            $content = str_replace( $k, $v, $content );
        }
        // 函数
        return $this -> applyPHP( 'if( ! defined( \'OS_PATH\' ) ): exit; endif;' ) . "\r\n" . preg_replace( '/\?>\s*?(?=<\?PHP\scase)/', '?>', $content );
    }

    /**
     * parseMain 方法，解析主要数据
     *
     * @access protected
     * @param String : $content, 模板内容
     * @param String : $fromParent, 来自父标签内
     * @return String
     */
    protected function parseMain( $content, $fromParent = NULL ) {

        // 解析父标签块的内容，优先子标签，重新排序
        if( ! empty( $fromParent ) ) {
            $this -> sortSubsetPriority( $fromParent );
        }

        foreach( $this -> Methods as $method => $attr ) {

            // 父级方法
            $parent = isset( $attr[ 'parent' ] ) ? explode( ',', $attr[ 'parent' ] ) : NULL;

            // 需要参数或可选参数
            $optional = ( isset( $attr[ 'optional' ] ) && ! empty( $attr[ 'optional' ] ) ) ? TRUE : FALSE;
            $required = $optional || ( isset( $attr[ 'required' ] ) && ! empty( $attr[ 'required' ] ) ) ? TRUE : FALSE;
            $maybeParam = $optional || $required ? TRUE : FALSE;
            // 配对的标签块
            $block = isset( $attr[ 'close' ] ) ? ( Boolean ) $attr[ 'close' ] : FALSE;
            // 平衡标签
            $balance = isset( $attr[ 'balance' ] ) ? ( Boolean ) $attr[ 'balance' ] : FALSE;
            // 是否多行
            $multiLine = isset( $attr[ 'multi-line' ] ) ? ( Boolean ) $attr[ 'multi-line' ] : FALSE;
            // 条件表达式
            $condition = isset( $attr[ 'condition' ] ) ? ( Boolean ) $attr[ 'condition' ] : FALSE;


            // 属性关联: [平衡标签]强制单标签
            $block = $balance ? FALSE : $block;
            // 属性关联: [标签多行]强制单标签
            $block = $multiLine ? FALSE : $block;
            // 属性关联: [平衡标签] 强制无参数
            $maybeParam = $balance ? FALSE : $required;

            /* 特殊过滤,提高效率和严谨性 */

            // 指定了父标签，只能在父标签中被解析
            if( ! empty( $parent ) && ! in_array( $fromParent, $parent ) ) {
                continue;
            }
            // 非配对标签块只解析一次,有父标签限制除外
            if( ! $block && is_null( $parent ) && ! is_null( $fromParent ) ) {
                continue;
            }

            // 替换解析模板内容
            $content = $this -> methodAutoReplace( $content, $method, $maybeParam, $block, $balance, $multiLine, $condition, $parent );

        }

        return $content;
    }

    /**
     * methodAutoReplace 方法，自动根据方法属性替换解析
     *
     * @access private
     * @param String : $content, 模板内容
     * @param String : $method, 模板方法名称
     * @param Boolean : $required, 需要参数
     * @param Boolean : $block, 块
     * @param Boolean : $balance, 是否平衡标签
     * @param Boolean : $multiLine, 标签多行
     * @param Boolean : $condition, 条件表达式
     * @param String : $parent, 父方法名称
     * @return String
     */
    private function methodAutoReplace( $content, $method, $required, $block, $balance, $multiLine, $condition, $parent ) {

        // 标签内容距离定界符前后最大允许空白
        $methodBegin = self :: TPL_PARSE_BLANK_SPACE . $method;
        $methodEnd = self :: TPL_PARSE_BLANK_SPACE . '\/' . $method . self :: TPL_PARSE_BLANK_SPACE;

        // 多行或单行模式
        $modifier = ( $block || $multiLine ) ? 'eis' : 'ei';

        // 回调方法的参数
        $callParam = array( $method, NULL, NULL, NULL, NULL );

        // 解析替换规则
        $left = $methodBegin;
        $body = '';
        $right = '';

        // 特殊标签,平衡标签, 最高优先级别
        if( $balance ) {
            $left .= '(.+?)' . $method . self :: TPL_PARSE_BLANK_SPACE;
            $callParam = array( $method, NULL, "\$1", "\$2", "\$3" );
        // 通常标签,有参数的
        } else if( $required || $condition ) {
            $left .= '\s+' . self :: TPL_PARSE_BLANK_SPACE;
            $left .= '\s*?(.+?)\s*?';
            $callParam[ 1 ] = "\$1";
        } else {
            $left .= self :: TPL_PARSE_BLANK_SPACE;
        }

        if( $block ) {
            $body = '(.+?)';
            $right = $methodEnd;
            if( $required || $condition ) {
                $callParam[ 2 ] = "\$2";
                $callParam[ 3 ] = "\$3";
                $callParam[ 4 ] = "\$4";
            } else {
                $callParam[ 2 ] = "\$1";
                $callParam[ 3 ] = "\$2";
                $callParam[ 4 ] = "\$3";
            }
        // 非平衡标签
        } else if( ! $balance ) {
            if( $required | $condition ) {
                $callParam[ 3 ] = "\$2";
                $callParam[ 4 ] = "\$3";
            } else {
                $callParam[ 3 ] = "\$1";
                $callParam[ 4 ] = "\$2";
            }
        }

        $regular = $this -> applyDelimiter( $left ) . $body;
        $regular .= $block ? $this -> applyDelimiter( $right ) : '';
        $maybeReg = $regular . '(.*?)';
        $regular .= '(.*?)' . $this -> getLineTagReg();

        // 平衡标签或块标签都需要内容( 一个在标签里，一个在闭合块里)
        $needContent = ( Boolean ) ( $block || $balance );
        $strictExist = preg_match( '/' . $regular . '/' . $modifier, $content );
        $maybeExist = preg_match( '/' . $maybeReg . '/' . $modifier, $content );
        while( $strictExist || $maybeExist ) {
            if( ! $strictExist && $maybeExist ) {
                $content = preg_replace( '/' . $maybeReg . '/' . $modifier, "\$this -> replaceInternalMethod( \$callParam[ 0 ], '{$callParam[ 1 ]}', '{$callParam[ 2 ]}', '{$this -> InternalLine}', \$required, \$needContent, \$condition, '{$callParam[ 3 ]}', TRUE )", $content );
            } else {
                $content = preg_replace( '/' . $regular . '/' . $modifier, "\$this -> replaceInternalMethod( \$callParam[ 0 ], '{$callParam[ 1 ]}', '{$callParam[ 2 ]}', '{$callParam[ 4 ]}', \$required, \$needContent, \$condition, '{$callParam[ 3 ]}' )", $content );
            }
            if( ! is_null( $parent ) || ! $block ) {
                break;
            }
            $strictExist = preg_match( '/' . $regular . '/' . $modifier, $content );
            $maybeExist = preg_match( '/' . $maybeReg . '/' . $modifier, $content );
        }

        return $content;
    }

    /**
     * replaceInternalMethod 方法，替换模板方法
     *
     * @callback
     * @access protected
     * @param String : $method, 方法名
     * @param String : $attr, 参数
     * @param String : $content, 内容
     * @param Integer : $line, 行号
     * @param Integer : $required, 需要参数
     * @param Integer : $block, 需要内容
     * @param Boolean : $condition, 是否为条件表达式
     * @param String : $otherString, 当前行的其它字符串
     * @param Boolean : $inLine, 来自同一行的标签
     * @return String
     */
    protected function replaceInternalMethod( $method, $attr = NULL, $content = NULL, $line, $required, $block, $condition, $otherString = NULL, $inLine = FALSE ) {

        $this -> InternalLine = $line;

        // 禁止在载入的模板中再次载入
        if( $method == 'include' && ! is_null( $this -> openTPL -> multiTemplate ) ) {
            $this -> throwException( Cache :: lang( 'NOT_LOAD_IN_TEMPLATE' ), $line );
        }

        $method = strtolower( $method );
        $attr = stripslashes( $attr );
        $otherString = str_replace( '\"', '"', $otherString );
        if( $method == 'javascript' ) {
            $content = str_replace( '\"', '"', $content );
        } else {
            $content = stripslashes( $content );
        }
        if( ini_get( 'magic_quotes_sybase' ) ) {
            $attr =  str_replace( '\"', '\'', $attr );
            $otherString = str_replace( '\"', '\'', $otherString );
        }
        // 该方法可能是某个方法的别名
        if( isset( $this -> Methods[ $method ] ) && isset( $this -> Methods[ $method ][ 'bind' ] ) ) {
            $openMethod = $this -> tagLibName . '_' . $this -> Methods[ $method ][ 'bind' ];
        } else {
            $openMethod = $this -> tagLibName . '_' . $method;
        }
        // 如果是一个块标签，并且内容是多行， 定位起始行号
        if( isset( $this -> Methods[ $method ][ 'close' ] ) && $this -> Methods[ $method ][ 'close' ] && ( strpos( $content, "\n" ) || strpos( $content, "\r" ) ) ) {
            $startLine = $this -> getBlockStartLine( $content );
            $line = $startLine ? $startLine : $line;
        }

        // 属性集合
        $args = array( 'attr' => stripslashes( $attr ),  'content' => $content, 'method' => $method, 'line' => $line );

        // 获取属性参数并验证
        if( $required && ! $condition ) {
            $params = $this -> requiredParamCheck( $args );
        // 条件表达式
        } else if( $condition ) {
            $params = $this -> parseCondition( $args[ 'attr' ] );
        }
        // 获取块内容
        if( $block ) {
            $blockContent = $this -> getByContent( $args );
        }

        // 根据方法属性自动使用参数调用
        if( method_exists( $this, $openMethod ) ) {
            if( isset( $params ) && isset( $blockContent ) ) {
                $result = $this -> $openMethod( $params, $blockContent );
            } else if( isset( $params ) ) {
                $result = $this -> $openMethod( $params );
            } else if( isset( $blockContent ) ) {
                $result = $this -> $openMethod( $blockContent );
            } else {
                $result = $this -> $openMethod();
            }
        }
        // 连接同行上剩余字符串并追加内部标记
        if( ! empty( $otherString ) ) {
            $result .= $otherString;
        }
        if( ! $inLine ) {
            $result = string_append_replace( $result, str_replace( '\\', '', $this -> getLineTagReg( $line ) ) );
        }
        return $result;
    }

    /**
     * replaceInternalRow 方法，单行标签解析
     *
     * @access protected
     * @param String : $variable, 变量标签
     * @param String : $otherString, 剩余未解析字符串
     * @param Integer : $line, 行号
     * @return String
     */
    protected function replaceInternalRow( $variable, $otherString = NULL, $line ) {
        $backup = $variable;
        $temp = '';
        // 删除内部标记
        if( is_null( $variable ) || strpos( $variable, $this -> openTPL -> internalLineTag ) === 0 ) {
            return '';
        }
        $variable = str_replace( '\"', '"', $variable );
        $otherString = str_replace( '\"', '"', $otherString );

        // 还原未知标签
        if( preg_match( '/^[^\d|^\w|^\$|^\'|^\"|^\/]/', trim( $variable ) ) ) {
            // 还原成标签
            $backup = $this -> tagRestore( $backup );
             // 递归解析剩余字符串
            if( ! empty( $otherString ) && preg_match( '/' . $this -> applyDelimiter( '.+?' ) . '/', $otherString ) ) {
                $otherString = preg_replace( '/' . $this -> applyDelimiter( '(.*?)' ) . '(.*)' . '/e', "\$this -> replaceInternalRow( '\$1', '\$2', \$line )", $otherString );
            }
            return $backup . $otherString;
        }

        // Begin 判断也许标签存在, 但可能存在异常
        $tagName = strtolower( $variable );
        $noClose = FALSE;
        $noParam = TRUE;
        if( preg_match( '/(.*?)\s/', $tagName, $temp ) ) {
            $tagName = $temp [ 1 ];
            $noParam = FALSE;
        }
        if( strpos( $variable, '/' ) === 0 ) {
            $noClose = TRUE;
            $tagName = substr( $tagName, 1 );
        }

        // 标签存在，开始判断
        if( isset( $this -> Methods[ $tagName ] ) ) {

            $condition = isset( $this -> Methods[ $tagName ][ 'condition' ] ) ? TRUE : FALSE;
            $block = ( isset( $this -> Methods[ $tagName ][ 'close' ] ) && $this -> Methods[ $tagName ][ 'close' ] ) ? TRUE : FALSE;
            $required = $condition || ( isset( $this -> Methods[ $tagName ][ 'required' ] ) && $this -> Methods[ $tagName ][ 'required' ] ) ? TRUE : FALSE;
            $parent = isset( $this -> Methods[ $tagName ][ 'parent' ] ) ? $this -> Methods[ $tagName ][ 'parent' ] : NULL;

            // 可能是未闭合
            if( $block && ( $noClose || ! $noParam ) ) {
                $this -> throwException( Cache :: lang( 'NO_CLOSE_TAG' ) . ': "' . $tagName . '"', $line );
            // 缺少必要参数
            } else if( $noParam && $required ) {
                if( $condition ) {
                    // Missing Condition
                    $this -> throwException( Cache :: lang( 'MISSING_CONDITION' ) . ': "' . $tagName . '"', $line );
                } else {
                    // Missing Attribute
                    $this -> throwException( Cache :: lang( 'MISSING_PARAM' ) . ': "' . $tagName . '"', $line );
                }
            // 此标签只能出现在指定标签块中
            } else if( $parent ) {
                $this -> throwException( Cache :: lang( 'ACCIDENT_LOCATION_TAG' ) . ': "' . $tagName . '", ' . Cache :: lang( 'APPEARS_ONLY_TAG' ) . ' "' . $this -> Methods[ $tagName ][ 'parent' ] . '"', $line );
            }
        }
        // End 判断也许标签存在, 但可能存在异常

        // 变量及函数解析
        if( preg_match( '/^[\d|\$|\'|\"]/', $variable ) ) {
            $variable = $this -> parseVariableTag( $variable, $line );
            $variable = $this -> applyEcho( $variable );
        // 还原标签
        } else {
            $variable = $this -> tagRestore( $backup );
        }

        // 递归解析剩余字符串
        if( ! empty( $otherString ) && preg_match( '/' . $this -> applyDelimiter( '.+?' ) . '/', $otherString ) ) {
            $otherString = preg_replace( '/' . $this -> applyDelimiter( '(.*?)' ) . '(.*)' . '/e', "\$this -> replaceInternalRow( '\$1', '\$2', \$line )", $otherString );
        }

        $variable .= $otherString;
        return $variable;
    }

    /**
     * parseCondition 方法，解析一个条件表达式
     *
     * @access protected
     * @param String : $condition
     * @return String
     */
    protected function parseCondition( $condition ) {
        $condition = str_ireplace( array_keys( $this -> comparison ), array_values( $this -> comparison ), trim( $condition ) );
        $result = '';
        // 解析变量
        foreach( explode( ' ', $condition ) as $var ) {
            if( $var{0} == '$' && strpos( $var, '.' ) ) {
                $result .= $this -> parseInternalVar( $var );
            } else {
                $result .= $var;
            }
        }
        return $result;
    }

    /**
     * parseAttrOf 方法，从字符串解析属性参数数组
     *
     * @access protected
     * @param Array : $args
     * @return Array
     */
    protected function parseAttrOf( $args ) {
        $string = $this -> getByAttr( $args );
        $string = preg_replace( array( '/(\s*)=(\s*)/', '/\s+/', '/(\s*)\[(\s*)/', '/(\s*)\](\s*)/' ), array( '=', ' ', '[', ']' ), trim( $string ) );
        $attr = array();
        foreach( explode( ' ', $string ) as $str ) {
            if( strpos( $str, '=' ) !== FALSE ) {
                $_temp = explode( '=', $str );
                $this -> InternalLine = $this -> getByLine( $args );
                $attr[ $_temp[ 0 ] ] = $this -> parseInternalAttr( $_temp[ 1 ] );
            } else {
                $attr[ $str ] = NULL;
            }
        }
        return $attr;
    }

    /**
     * parseVariableTag 方法，解析模板变量或函数的字符串
     *
     * @access protected
     * @param String : $variable
     * @param Integer : $line
     * @return String
     */
    protected function parseVariableTag( $variable, $line = 0 ) {

        // 进入内部解析，实际还在同一行上
        if( $line ) {
            $this -> InternalLine = $line;
        }

        $spacePosition = strpos( $variable, self :: TPL_FUNC_SEP );
        // 使用了模板函数
        if( $spacePosition ) {
            $var = substr( $variable, 0, $spacePosition );
            $func = $this -> VariableTagString2Array( substr( $variable, $spacePosition + 1 ), self :: TPL_FUNC_SEP );
            $var = $this -> parseInternalVar( $var );
            foreach( $func as $f ) {
                $var = $this -> parseInternalFunc( $f, $var ) ;
            }
        // 仅仅是模板变量
        } else {
            $var = $this -> parseInternalVar( $variable );
        }
        return trim( $var );
    }

    /**
     * VariableTagString2Array 方法，解析模板变量字符串
     *
     * @access protected
     * @param String : $string
     * @param String : $keyword
     * @return Array
     */
    private function VariableTagString2Array( $string, $keyword ) {
        $string = trim( $string );
        $func = array();
        $quote = array( 0, 0 );
        $str = '';
        for( $i = 0, $n = strlen( $string ); $i < $n; $i ++ ) {
            $str .= $string{ $i };
            switch( $string{ $i } ) {
                case '\'' : $quote[ 0 ] ++; break;
                case '"' : $quote[ 1 ] ++; break;
                case $keyword :
                    if( array_sum( $quote ) % 2 == 0 ) {
                        $func[] = trim( string_after_remove( $str, $keyword ) );
                        $str = '';
                    } else {
                        continue;
                    }
                    break;
                default :
                    if( $i + 1 == strlen( $string ) ) {
                        $func[] = trim( string_after_remove( $str, $keyword ) );
                    }
            }
        }
        if( in_array( $string{ $i - 1 }, array( '\'', '"' ) ) ) {
            $func[] = $str;
        }
        return $func;
    }

    /**
     * parseInternalArrayKey 方法，解析数组里的键名
     *
     * @access protected
     * @param String : $key
     * @param String : $variableString, 当前正在解析的模板变量字符串
     * @return String
     */
    protected function parseInternalArrayKey( $key, $variableString ) {
        $key = trim( $key );
        // 数字常量键名
        if( is_numeric( $key ) ) {
            $key = '[ ' . $key . ' ]';
        // 字符串键名
        } else if( preg_match( '/\"(.*)\"|\'(.*)\'/', $key ) ) {
            $key = '[ \'' . stripslashes( string_double_remove( $key, array( '\'', '"' ) ) ) . '\' ]';
        // 变量键名
        } else if( $key{ 0 } == '$' && ! preg_match( '/\s/', $key ) ) {
            $key = '[ ' . $this -> parseInternalVar( $key ) . ' ]';
        // 错误
        } else {
            $this -> throwException( Cache :: lang( 'UNKNOWN_TEMPLATE_TAG' ) . ': "' . $variableString . '"', $this -> InternalLine );
        }
        return $key;
    }

    /**
     * parseInternalAttr 方法，解析模板标签方法参数的值
     *
     * @access private
     * @param String : $value
     * @return String
     */
    private function parseInternalAttr( $value ) {
        $value = trim( $value );
        $backup = $value;
        // 有引号的作为字符串
        if( preg_match( '/^[\"|\']/', $value ) ) {
            return string_double_remove( $value, array( '\'', '"' ) );
        // 如果不是数字开头，默认认为这个字符串是一个变量名
        } else if( ! preg_match( '/^[\d]/', $value ) ) {
            $value = string_prepend_replace( $value, '$' );
        }

        // 数组变量
        if( preg_match( '/\[(.+?)\]/', $value ) ) {
            $value = trim( substr( $value, 0, strpos( $value, '[' ) ) );
            if( preg_match( '/\s/', $value ) ) {
                $this -> throwException( Cache :: lang( 'TEMPLATE_PARAM_ERROR' ) . ': "' . $backup . '"', $this -> InternalLine );
            }
            $keyString = trim( substr( $value, strpos( $value, '[' ) ) );
            $value .= preg_replace( '/\[(.+?)\]/e', "\$this -> parseInternalArrayKey( '\$1', \$backup )", $keyString );
        }
        // 以"."连接的数组形式
        if( strpos( $value, '.' ) ) {
            $value = trim( substr( $value, 0, strpos( $value, '.' ) ) );
            if( preg_match( '/\s/', $value ) ) {
                $this -> throwException( Cache :: lang( 'TEMPLATE_PARAM_ERROR' ) . ': "' . $backup . '"', $this -> InternalLine );
            }
            $keyString = trim( substr( $backup, strpos( $backup, '.' ) + 1 ) );
            foreach( explode( '.', $keyString ) as $v ) {
                if( preg_match( '/\[(.+?)\]/', $v ) ) {
                    $this -> throwException( Cache :: lang( 'TEMPLATE_PARAM_ERROR' ) . ': "' . $backup . '"', $this -> InternalLine );
                } else if( strpos( $v, '$' ) === 0 ) {
                    $value .= '[ ' . $v . ' ]';
                } else {
                    $value .= '[ \'' . $v . '\' ]';
                }
            }
         }
         return $value;
    }

    /**
     * parseInternalVar 方法，解析模板变量
     *
     * @access protected
     * @param String : $variable
     * @return String
     */
    protected function parseInternalVar( $variable ) {
        $backup = $variable;
        $variable = trim( $variable );
        // 模板变量
        if( $variable{ 0 } === '$' && strlen( $variable ) > 1 ) {
            // 数组变量
            if( preg_match( '/\[(.+?)\]/', $variable ) ) {
                $variable = trim( substr( $variable, 0, strpos( $variable, '[' ) ) );
                if( preg_match( '/\s/', $variable ) ) {
                    $this -> throwException( Cache :: lang( 'UNKNOWN_TEMPLATE_TAG' ) . ': "' . $backup . '"', $this -> InternalLine );
                }
                $keyString = trim( substr( $backup, strpos( $backup, '[' ) ) );
                $variable .= preg_replace( '/\[(.+?)\]/e', "\$this -> parseInternalArrayKey( '\$1', \$backup )", $keyString );
            }
            // 以"."连接的数组形式
            if( strpos( $variable, '.' ) ) {
                $variable = trim( substr( $variable, 0, strpos( $variable, '.' ) ) );
                if( preg_match( '/\s/', $variable ) ) {
                    $this -> throwException( Cache :: lang( 'UNKNOWN_TEMPLATE_TAG' ) . ': "' . $backup . '"', $this -> InternalLine );
                }
                $keyString = trim( substr( $backup, strpos( $backup, '.' ) + 1 ) );
                foreach( explode( '.', $keyString ) as $v ) {
                    if( preg_match( '/\[(.+?)\]/', $v ) ) {
                        $this -> throwException( Cache :: lang( 'UNKNOWN_TEMPLATE_TAG' ) . ': "' . $backup . '"', $this -> InternalLine );
                    } else if( strpos( $v, '$' ) === 0 ) {
                        $variable .= '[ ' . $v . ' ]';
                    } else {
                        $variable .= '[ \'' . $v . '\' ]';
                    }
                }
            }
            return $variable;

        // 字符串
        } else if( preg_match( '/\"(.*)\"|\'(.*)\'/', $variable ) ) {
            // 空字符串
            if( ! strlen( $variable ) ) {
                $variable = '\'\'';
            }
        } else {
            // 数字常量
            if( is_numeric( $variable ) && ! preg_match( '/\s/', $variable ) ) {
                return $variable;
            }
            /*! 规则更宽松，为了让多个标签方法可以写在同一行上
            // Unknown Tag
            if( preg_match( '/\s/', $variable ) ) {
                $tagName = substr( $backup, 0, strpos( $backup, ' ' ) );
            } else {
                $tagName = $backup;
            }
            $this -> throwException( Cache :: lang( 'UNKNOWN_TEMPLATE_TAG' ) . ': "' . $backup . '"', $this -> InternalLine );
            */
        }
        return $variable;
    }

    /**
     * parseInternalFunc 方法，解析模板函数
     *
     * @access protected
     * @param String : $func
     * @param String : $var
     * @return String
     */
    protected function parseInternalFunc( $func, $var ) {
        // 没有参数，模板变量做为唯一参数
        if( ! strpos( $func, self :: TPL_FUNC_END ) ) {
            $funcName = trim( $func );
        } else {
            $funcName = trim( substr( $func, 0, strpos( $func, self :: TPL_FUNC_END ) ) );
            $param = substr( $func, strpos( $func, self :: TPL_FUNC_END ) + 1 );
        }

        // PHP内置函数或模板扩展函数
        $funcRetName = $this -> getExtensionFunc( $funcName, $var );
        if( is_null( $funcRetName ) ) {
            $this -> throwException( Cache :: lang( 'FUNCTION_NOT_EXIST' ) . ': "' . $funcName . '"', $this -> InternalLine );
        }
        // 没有参数情况，把自身作为参数
        if( ! isset( $param ) ) {
            $params = array( $var );
        // 解析参数
        } else {
            $params = $this -> VariableTagString2Array( $param, self :: TPL_PARAM_SEP );
            foreach( $params as $i => $v ) {
                switch( trim( $v ) ) {
                    case self :: TPL_DELIMITER_LEFT :
                        $params[ $i ] = '\'' . $this -> openTPL -> left . '\'';
                        break;
                    case self :: TPL_DELIMITER_RIGHT :
                        $params[ $i ] = '\'' . $this -> openTPL -> right . '\'';
                        break;
                    case self :: TPL_VAR_SELF :
                        $params[ $i ] = $var;
                        break;
                    default :
                        $params[ $i ] = $this -> parseInternalVar( trim( $v ) );
                }
            }
        }
        $funcRetName .= implode( ', ', $params ) . ' )';
        return $funcRetName;
    }


    /**
     * tagRestore 方法，还原成模板标签
     *
     * @access protected
     * @param String : $content
     * @return String
     */
    protected function tagRestore( $content ) {
        return $this -> openTPL -> left . $content . $this -> openTPL -> right;
    }

    /**
     * requiredParamCheck 方法，方法的必要参数检查
     *
     * @access protected
     * @param Array : $args
     * @return Array : $attr
     */
    protected function requiredParamCheck( $args ) {
        $method = $this -> getByMethod( $args );
        // 获取并解析标签方法的属性参数
        $attr = $this -> parseAttrOf( $args );
        $line = $this -> getByLine( $args );
        // 获取方法的规则
        $rules = $this -> Methods[ $method ];

        // 检查方法的必要参数
        if( isset( $rules[ 'required' ] ) && ! empty( $rules[ 'required' ] ) ) {
            foreach( explode( ',', $rules[ 'required' ] ) as $mth ) {
                if( array_key_exists( $mth, $attr ) ) {
                    continue;
                } else {
                    $this -> throwException( Cache :: lang( 'MISSING_PARAM' ) . ': ' . $method . ' :: "' . $mth . '"', $line );
                }
            }
        }
        return $attr;
    }

    /**
     * restoreLiteralDataCallback 方法，还原临时替换的Literal文本内容
     *
     * @callback
     * @access private
     * @param Integer : $num
     * @return String
     */
    private function restoreLiteralDataCallback( $num ) {
        if( isset( $this -> literalData[ $num ] ) ) {
            $data = $this -> literalData[ $num ];
            // 删除内部标记
            $data = preg_replace( '/' . $this -> getLineTagReg() . '/e', "\$this -> replaceInternalRow( NULL, NULL, 0 )", $data );
            $data = stripslashes( $data );
            if( ini_get( 'magic_quotes_sybase' ) ) {
                $data =  str_replace( '\"', '\'', $data );
            }
            return $data;
        }
        return NULL;
    }

    /**
     * sortSubsetPriority 方法，按子集优先排序方法
     *
     * @access private
     * @param String : $parent
     * @return void
     */
    private function sortSubsetPriority( $parent ) {
        $temp = $this -> Methods;
        $this -> Methods = array();
        $after = array();
        foreach( $temp as $method => $attr ) {
            if( isset( $attr[ 'parent' ] ) && strpos( $attr[ 'parent' ], $parent ) !== FALSE ) {
                $this -> Methods[ $method ] = $attr;
            } else {
                $after[ $method ] = $attr;
            }
        }
        $this -> Methods = array_merge( $this -> Methods, $after );
    }

    /**
     * throw_exception 方法，抛出模板异常
     *
     * @access public
     * @param String : $msg, 错误信息
     * @param Integer : $line, 所在行号
     * @param Boolean : $extra, 显示额外信息
     * @return void
     */
    public function throwException( $msg, $line, $extra = TRUE ) {
        $file = is_null( $this -> openTPL -> multiTemplate ) ? $this -> openTPL -> mainTemplate : $this -> openTPL -> multiTemplate;
        $error = array(
            'message' => $msg,
            'line' => $line,
            'data' => $this -> openTPL -> data,
            'file' => $file,
            'extra' => $extra,
        );
        throw_exception( $error, $this -> internalException );
    }

     /**
     * getExtensionFunc 方法，获取函数名
     *
     * @access protected
     * @param String : $funcName, 函数名
     * @param * : $var
     * @return String
     */
    private function getExtensionFunc( $funcName, $var ) {
        // OpenTPL Extension Function
        if( strpos( $funcName, '.' ) ) {
            $path = '';
            $temp = explode( '.', $funcName );
            $name = trim( array_pop( $temp ) );
            foreach( $temp as $v ) {
                $path .= trim( ucfirst( strtolower( $v ) ) ) . DS;
            }
            $path = string_after_remove( $path, DS );
            $funcFile = $this -> openTPL -> ExtensionDir . $path . DS . 'function.' . strtolower( $name ) . '.php';
            if( ! is_file( $funcFile ) ) {
                return NULL;
            }
            return '$this -> tpl -> callExtensionFunc( \'' . $path . '\', \'' . strtolower( $name ) . '\', ' . $var . ', ';
        // PHP Internal Function
        } else if( function_exists( $funcName ) ) {
            return $funcName . '( ';
        }
        return NULL;
    }

    /**
     * 魔术方法, 一些简单功能合集
     *
     * @access protected
     * @param String : $method
     * @param Array : $args
     * @return Mixed
     */
    public function __call( $method, $args ) {
        if( substr( $method, 0, 5 ) == 'getBy' && is_array( $args[ 0 ] ) ) {
            $of = strtolower( substr( $method, 5 ) );
            return $args[ 0 ][ $of ];
        } else if( substr( $method, 0, 5 ) == 'apply' ) {
            $type = strtolower( substr( $method, 5 ) );
            switch( $type ) {
                case 'php' : return '<?PHP ' . $args[ 0 ] . ' ?>'; break;
                case 'echo' : return $this -> applyPHP( 'echo( ' . $args[ 0 ] . ' );' ); break;
                case 'delimiter' : return $this -> left . $args[ 0 ] . $this -> right; break;
                case 'tplmethodblock' : return $this -> applyDelimiter( $args[ 0 ] ) . $args[ 1 ] . $this -> applyDelimiter( '\/' . $args[ 2 ] ); break;
            }
        } else if( $method == 'getLineTagReg' ) {
            $value = isset( $args[ 0 ] ) ? $args[ 0 ] : '(.+?)';
            return $this -> applyDelimiter( $this -> openTPL -> internalLineTag . $value );
        } else if( $method == 'getBlockStartLine' ) {
            $line = 0;
            if( preg_match( '/' . $this -> getLineTagReg() . '/', $args[ 0 ], $temp ) ) {
                $line = $temp[ 1 ];
            }
            return $line;
        }
        return NULL;
    }

}
?>